package owlapps2012.owlToSql.base;

import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;


/*
 * Assumptions:
 * Expressions must have tree-like structure representing the nested realations.
 * Tree level is marked by tab sign (\t)
 * occurs_before and occurs_after will be translated to occurs_1_[TIME_UNIT]_before/occurs_1_[TIME_UNIT]_after
 * 
 * Method:
 * Only one node from any level can be active at a time,
 * Root node is always active
 * ArrayList stores the current active node for every level
 */
public class TemporalExpressionAnalyzer
{
	private static Logger log = Logger.getLogger(TemporalExpressionAnalyzer.class);

	private HashMap<Integer, IntNode> activeNodesList = new HashMap<Integer, IntNode>(); 
	private StringBuffer structureRepresentation = new StringBuffer(); 
	private String expressionToTransform; 
	private IntNode rootNode;
	private boolean isAlreadyAnalyzed;
	
	private static final String TIME_UNIT = "day";
	
	/*
	 * Capturing groups:
	 * -----------------
	 * 1 - tabs
	 * 2 - temporal relation
	 * 3 - occurs_max_N_days
	 * 4 - occurs_max_N_days: number of days
	 * 5 - occurs_between_X_and_Y: 
	 * 6 - occurs_between_X_and_Y: number of days
	 * 7 - occurs_N_day(s): 
	 * 8 - occurs_N_day(s): number of days
	 * 9 - occurs_simultaneously
	 * 10 - occurs_min_N_days
	 * 11 - before 
	 */
	private static final Pattern temporalRelationPattern = Pattern.compile("(\\t{1,})\\({0,}((occurs_max_([0-9]{1,}))|(occurs_between_[0-9]{1,}_and_([0-9]{1,}))|(occurs_([0-9]{1,}))|(occurs_simultaneously)|(occurs_min_[0-9]{1,}))([a-z_]{1,}after){0,}");
	
	public TemporalExpressionAnalyzer(String expressionToTransform) 
	{
		this.expressionToTransform = expressionToTransform;
		this.handleOpenTimeRanges();
	}

	private void handleOpenTimeRanges() 
	{
		this.expressionToTransform = this.expressionToTransform.replaceAll("occurs_after", "occurs_1_"+TIME_UNIT+"_after").replaceAll("occurs_before", "occurs_1_"+TIME_UNIT+"_before");
	}

	private int getMaxNumberOfDays(Matcher m) throws Exception 
	{
		String res;
		if(m.group(3) != null)
			res =  m.group(4);
		else if(m.group(5) != null)
			res =  m.group(6);
		else if(m.group(7) != null)
			res =  m.group(8);
		else if(m.group(9) != null)
			res =  "0";
		else if(m.group(10) != null)
			throw new Exception("Unsupported temporal relation: "+m.group());
		else
			throw new Exception("Unsupported temporal relation: "+m.group());
		
		if(m.group(11) != null)
			res = "-".concat(res);
		
		return Integer.parseInt(res);
	}


	public void analyzeExpression() throws Exception 
	{
		if(!this.isAlreadyAnalyzed)
		{
			log.info("Analyzing expression...");
			this.prepareExpressionTree();
			if(this.rootNode != null)
				this.rootNode.accumulateValuesOfChildren();
			else
				throw new Exception("Unable to parse expression - make sure it contains indented temporal relations (consider using EventRendering.transformRendering(String expr)): "+this.expressionToTransform);
			this.isAlreadyAnalyzed = true;
			log.info("Expression succesfully analyzed!");
		}
	}
	
	public int determineBackwardValidityOfAnExpresion() throws Exception
	{
		if(this.expressionToTransform.contains("after"))
		{
			if(!this.isAlreadyAnalyzed)
				this.analyzeExpression();
			return Math.abs(this.rootNode.getCumulatedMinValue());
		}
		else 
			return 0;
	}
	
	public int determineForwardValidityOfAnExpresion() throws Exception
	{
		if(this.expressionToTransform.contains("before"))
		{
			if(!this.isAlreadyAnalyzed)
				this.analyzeExpression();
			return this.rootNode.getCumulatedMaxValue();
		}
		else 
			return 0;
	}
	
	private void prepareExpressionTree() throws Exception
	{
		Matcher m = temporalRelationPattern.matcher(this.expressionToTransform);
		while(m.find())
		{
			log.info(m.group());
//			int lastLevel;
			int newLevel;
			if(this.rootNode == null)
			{
				this.rootNode = new IntNode(0);
				this.activeNodesList.put(Integer.valueOf(0), this.rootNode);
//				lastLevel = 0;
			}
//			else
//			{
//				lastLevel = activeNodesList.size();
//			}
			newLevel = m.group(1).length();
			int numberOfDays = getMaxNumberOfDays(m);
			IntNode currentNode = new IntNode(numberOfDays);
			getActiveNodeFromLevel(newLevel-1).addChildNode(currentNode);
			setActiveNodeForLevel(newLevel, currentNode);
			updateStructureRepresentation(newLevel, numberOfDays);
		}
	}


	private void updateStructureRepresentation(int newLevel, int numberOfDays) 
	{
		String levelSign = "";
		for(int i=0;i<newLevel;i++)
			levelSign = levelSign.concat("*");
		this.structureRepresentation.append(levelSign.concat(String.valueOf(numberOfDays)).concat("\n"));
	}

	private void setActiveNodeForLevel(int newLevel, IntNode node) throws Exception
	{
		if(newLevel == 0)
			throw new Exception("Unable to reset root level!");
		for(int i=this.activeNodesList.size();i>newLevel;i--)
		{
			this.activeNodesList.remove(i);
		}
		this.activeNodesList.put(Integer.valueOf(newLevel), node);
	}


	private IntNode getActiveNodeFromLevel(int i) 
	{
		return this.activeNodesList.get(i);
	}
	

	public StringBuffer getStructureRepresentation() 
	{
		return structureRepresentation;
	}
	
}
